package com.ra.util;

import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Random;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.apache.struts2.ServletActionContext;
import org.springframework.stereotype.Service;

import com.espirit.eap.ValidationUtil;
import com.espirit.eap.manager.SystemDataService;
import com.espirit.eap.sa.LoginUser;
import com.espirit.eap.sa.LoginUserService;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.annotation.Rest;
import com.googlecode.cswish.exception.ApplicationException;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.struts.spring.CacheService;
import com.googlecode.cswish.struts.spring.GenericService;
import com.googlecode.cswish.util.WebsiteUtil;

/**
 * SMS utility class
 * 
 * @author Jerry.feng 2014-09-04
 * 
 */
@Service
public class SmsService {
	
	public final static Integer CHANEL_BAK = 0;
	public final static Integer CHANEL_NORMAL = 1;
	
	public final static String SYSTEMDATA_USE_SMS_VERIFY_CODE = "useSmsVerifyCode";
	public final static String SYSTEMDATA_AUTO_SMS_VERIFY_CODE = "autoSmsVerifyCode";
	
	private Cipher cipher;
	
	// default value
	private class SmsChanel {
		private String smsUrl;
		private String smsResultBeginFlag;
		private String smsResultEndFlag;
		public SmsChanel(String smsUrl, String smsResultBeginFlag,
				String smsResultEndFlag) {
			super();
			this.smsUrl = smsUrl;
			this.smsResultBeginFlag = smsResultBeginFlag;
			this.smsResultEndFlag = smsResultEndFlag;
		}
	}
	
	// mdgxsend：内容不同，手机号不同的，多个内容和多个手机号中间以英文的逗号分隔，内容条数和手机号个数要求一一对应.
	private SmsChanel[] smsChanels = new SmsChanel[] {
			new SmsChanel("http://218.244.136.70:8888/sms.aspx?action=send&userid=188&account=房讯软件&password=123456&mobile=%s&content=%s&sendTime=&extno="
					, "<returnstatus>", "</returnstatus>"),
			new SmsChanel("http://sdk.entinfo.cn:8061/webservice.asmx/mdsmssend?sn=SDK-SKY-010-02771&pwd=7BA7FD98438A9E0DFACF51CAE9EFD3FD&mobile=%s&content=%s&ext=&stime=&rrid=&msgfmt="
					, "<string xmlns=\"http://entinfo.cn/\">", "</string>")
	};
		
	private static final Log logger = LogFactory.getLog(SmsService.class);
	
	@Resource
	private SystemDataService systemDataService;
	
	@Resource
	private CacheService cacheService;
	
	@Resource
	private GenericService genericService;
	
	@Resource
	private LoginUserService loginUserService;
	
	@Resource
	private ValidationUtil validationUtil;
	
	// 统计不同ip的sms发送的次数
	private int checkTime;		// 统计时间 (天)
	private Map<String, Integer> requestCount;		// sms请求次数, ip <--> count
	private Map<String, String> picCodeMapping;		// 电话号码与验证码

	@PostConstruct
	private void init() {
		requestCount = cacheService.newCache(Integer.class, 100000);
		picCodeMapping = cacheService.newCache(String.class, 100000);
	}

	/**
	 * 
	 * @param mobile
	 * @param type 1 --注册; 2 --忘记密码; 3 --申请改变账号身份为房东管家; 4 --申请绑定微信号 
	 * @return
	 */
	/*@Rest(simpleServiceName = "anonymity", type = "free")
	@Deprecated
	public boolean sendVerifyCode(String mobile, int type, boolean useLen4) {
		//FIXME: remove the function in future
		if (useVerifyCode()) {
			HttpServletRequest request = ServletActionContext.getRequest();
			String userAgent = request.getHeader("User-Agent");
			boolean isIphone = false;
			boolean isAndroid = false;
			if (userAgent != null){
				userAgent = userAgent.toLowerCase();
				if (userAgent.indexOf("iphone") != -1) {
					isIphone = true;
				}
				
				if (userAgent.indexOf("android") != -1) {
					isAndroid = true;
				}
			}
			
			if (!isIphone && !isAndroid) {
				String queryStr = request.getQueryString();
				if (!StringUtils.isEmpty(queryStr)) {
					logger.error("Found illegar request, queryStr: " + queryStr);
					return true;
				}
			}
		}
		
		return sendMessageToMobile(mobile, type, null, useLen4);
	}*/
	
	public boolean useVerifyCode() {
		return systemDataService.getValue(SYSTEMDATA_USE_SMS_VERIFY_CODE, true, true);
	}
	
	public void bindMobileVerifyKey(String mobile, String key) {
		picCodeMapping.put(mobile, key);
	}
	
	public boolean sendVerifyCodeByWeiXin(String mobile, int type, boolean useLen4) {
		return sendMessageToMobile(mobile, type, null, useLen4);
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public boolean sendVerifyCodeByIphone(String mobile, int type, String key, boolean useLen4) {
		checkFreeType(type);
		
		String SEED = null;
		String[] keys = decrypt(key, SEED);
		String decryptMobile = keys[0];
		if (!StringUtils.equals(mobile, decryptMobile)) {
			throw new ApplicationException("非法请求");
		}
		
		return sendMessageToMobile(mobile, type, keys[1], useLen4);
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public boolean sendVerifyCodeByAndroid(String mobile, int type, String key, boolean useLen4) {
		checkFreeType(type);
		
		String SEED = "1993912221063121745960308";
		String[] keys = decrypt(key, SEED);
		String decryptMobile = keys[0];
		if (!StringUtils.equals(mobile, decryptMobile)) {
			throw new ApplicationException("非法请求");
		}
		
		return sendMessageToMobile(mobile, type, keys[1], useLen4);
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public boolean sendVerifyCodeByCAndroid(String mobile, int type, String key, boolean useLen4) {
		checkFreeType(type);
		
		String SEED = "1993912221063122030062039";
		String[] keys = decrypt(key, SEED);
		String decryptMobile = keys[0];
		if (!StringUtils.equals(mobile, decryptMobile)) {
			throw new ApplicationException("非法请求");
		}
		
		return sendMessageToMobile(mobile, type, keys[1], useLen4);
	}
	
	@Rest(simpleServiceName = "anonymity", type = "free")
	public boolean sendVerifyCodeByCIphone(String mobile, int type, String key, boolean useLen4) {
		checkFreeType(type);
		
		String SEED = null;
		String[] keys = decrypt(key, SEED);
		String decryptMobile = keys[0];
		if (!StringUtils.equals(mobile, decryptMobile)) {
			throw new ApplicationException("非法请求");
		}
		
		return sendMessageToMobile(mobile, type, keys[1], useLen4);
	}

	@Rest(simpleServiceName = "anonymity", type = "free")
	public boolean sendVerifyCodeByKey(String mobile, int type, String key, boolean useLen4) {
		checkFreeType(type);
		
		boolean pass = false;
		String mobileKey = picCodeMapping.get(mobile);
		
		if (mobileKey != null) {
			if (StringUtils.isNotEmpty(key) && StringUtils.equals(key.toUpperCase(), mobileKey.toUpperCase())) {
				pass = true;
			}
			picCodeMapping.remove(mobile);
		}
		
		if (useVerifyCode()) {
			if (!pass) {
				throw new ApplicationException("无效的图形验证码");
			}
		}
		
		HttpServletRequest request = ServletActionContext.getRequest();
		String extFlag = request.getSession().getId();
		return sendMessageToMobile(mobile, type, extFlag, useLen4);
	}
	
	/**
	 * WAP手机网页注册页面API
	 * @param mobile
	 * @param type
	 * @param key
	 * @param useLen4
	 * @return
	 */
	@Rest(simpleServiceName = "anonymity", type = "free")
	public boolean sendVerifyCodeByWap(String mobile, int type, String key) {
		checkFreeType(type);
		
		HttpServletRequest request = ServletActionContext.getRequest();
		// TODO: check the browser type
		String extFlag = request.getSession().getId();
		return sendMessageToMobile(mobile, type, extFlag, true);
	}

	private void checkFreeType(int type) {
		if (type != SmsLog.TYPE_REGISTER && type != SmsLog.TYPE_RESET_PASSWORD && type != SmsLog.TYPE_BIND_WEIXIN && type != SmsLog.TYPE_SWITCH_ASSIST) {		// TYPE_BIND_WEIXIN, TYPE_SWITCH_ASSIST
			throw new ApplicationException("无效参数");
		}
	}
	
	private void checkPhone(String mobile, int type) {
		if (type == SmsLog.TYPE_REGISTER) {
			LoginUser loginUser = new LoginUser();
			loginUser.setName(mobile);
			if (validationUtil.hasDuplicateProperty(loginUser)) {
				throw new ApplicationException("该手机号已经被注册,您可以直接登录使用,如忘记密码,可申请重置");
			}
		}
	}
	
	private boolean sendMessageToMobile(String mobile, int type, String extFlag, boolean useLen4) {
		if (StringUtils.isEmpty(mobile)) {
			throw new ApplicationException("请输入手机号");
		}
		if (!NumberUtils.isDigits(mobile)) {
			throw new ApplicationException("无效的手机号码");
		}
		
		checkPhone(mobile, type);
		
		String ip = WebsiteUtil.getRemoteIp(ServletActionContext.getRequest());
		if (logger.isInfoEnabled()) {
			logger.info("send verify code to mobile:" + mobile + ", request from ip:" + ip);
		}
		
		// 检查IP或手机号, 防止恶意的申请
		int curDay = Calendar.getInstance().get(Calendar.DAY_OF_YEAR);
		if (curDay == checkTime) {
			Integer count = requestCount.get(ip);
			if (count == null) {
				count = 0;
			}
			if (count >= 50) {		// 同一ip， 一天最多申请一50次
				throw new ApplicationException("申请验证码次数过多,请改天再次申请");
			} else {
				requestCount.put(ip, ++count);
			}
		} else {
			requestCount.clear();
			checkTime = curDay;
		}
		
		// 检查该号码的上一次的发送时间， 不可以太过于频繁
		SmsInfo smsInfo = getMobileSmsInfo(mobile);
		if (smsInfo != null) {
			if (Calendar.getInstance().getTimeInMillis() - smsInfo.getCreateDate().getTime() < 120000) {	// 120s
				throw new ApplicationException("申请验证码过于频繁,请过会重新申请");
			}
		}
		
		// 同一号码或同一标志符, 每天最多申请5条
		String ql = "from " + SmsLog.class.getName() + " a where a.sendDate>=? and a.sendDate<? and (a.mobile=?" + (extFlag == null ? "" : " or a.extFlag=?") + ")";
		Date sendDateBegin = DateUtils.truncate(Calendar.getInstance().getTime(), Calendar.DAY_OF_MONTH);
		Date sendDateEnd = DateUtils.addDays(sendDateBegin, 1);
		QLInfo qlInfo = new QLInfo(ql, true, sendDateBegin, sendDateEnd, mobile);
		if (extFlag != null) {
			qlInfo.addParam(extFlag);
		}
		int sendCount = genericService.getCount(qlInfo, false);
		if (sendCount >= 10) {
			throw new ApplicationException("今天申请的验证码次数过多,请改天再次申请");
		}

		String verifyCode = generateVerifyCode(useLen4);
		String desc;
		if (type == SmsLog.TYPE_RESET_PASSWORD) {
			desc = "请完成验证, 重置密码";
		} else if (type == SmsLog.TYPE_SWITCH_ASSIST){
			desc = "请完成验证, 改变账号身份为房东管家";
		} else if (type == SmsLog.TYPE_BIND_WEIXIN) {
			desc = "请完成验证, 绑定微信号";
		} else {	// SMS_TYPE_REGISTER
			desc = "请完成验证注册";
		}
		String content = "验证码：" + verifyCode + ", " + desc + ", 有效期5分钟. 如非本人操作, 请忽略本短信.";
		
		LoginUser loginUser = loginUserService.getLoginUser();
		SmsLog smsLog = sendMessage(mobile, type, content, true, loginUser, CHANEL_NORMAL, extFlag);
		
		if (smsLog != null) {
			addMobileSmsInfo(mobile, verifyCode, smsLog.getId());
		}
		return smsLog != null;
	}
	
	private SmsInfo getMobileSmsInfo(String mobile) {
		if (StringUtils.isEmpty(mobile)) {
			return null;
		}
		
		SmsInfo info = new SmsInfo();
		info.setMobile(mobile);
		return genericService.searchAll(info).getData(0);
	}
	
	private void addMobileSmsInfo(String mobile, String verifyCode, Integer smsLogId) {
		SmsInfo smsInfo = getMobileSmsInfo(mobile);
		if (smsInfo == null) {
			smsInfo = new SmsInfo(mobile, verifyCode, smsLogId);
		} else {
			smsInfo.setCode(verifyCode);
			smsInfo.setSmsLogId(smsLogId);
			smsInfo.setCreateDate(Calendar.getInstance().getTime());
		}
		genericService.merge(smsInfo);
	}
	
	/**
	 * 
	 * @param mobile 目标手机
	 * @param type 消息类型, 不确定的类型设为SmsLog.TYPE_REMINDER_UNKNOWN
	 * @param content 消息内容
	 * @param free 是否免费发送
	 * @param loginUser 请求发送该信息的用户, 为空表示未知
	 * @param extFlag 扩展标志
	 * @return
	 */
	@Protected
	public SmsLog sendMessage(String mobile, Integer type, String content, boolean free, LoginUser loginUser, Integer channel, String extFlag) {
		if (CHANEL_BAK.equals(channel)) {
			content = StringUtils.replace(content, "【房讯万家】", "【房讯科技】");
			//content = StringUtils.replace(content, "【房讯万家】", "【好好租】");
		} else {
			if (!content.endsWith("【好好租】")) {
				//content += "【房讯万家】";
				content += "【好好租】";
			}
		}
		
		SmsChanel smsChanel = smsChanels[channel];
		String smsMessageUrl = String.format(smsChanel.smsUrl, mobile, content);
		boolean success = false;
		URI uri = null;
		try {
			URL url = new URL(smsMessageUrl);
			uri = new URI(url.getProtocol(), url.getHost() + ":" + url.getPort(), url.getPath(), url.getQuery(), null);
		} catch (MalformedURLException e1) {
			logger.error("Send SMS message", e1);
		} catch (URISyntaxException e) {
			logger.error("Send SMS message", e);
		}
		
		HttpClient httpclient = HttpClientBuilder.create().build();
		HttpGet httpGet = new HttpGet(uri);
		String returnStatus;
		try {
			HttpResponse httpResponse = httpclient.execute(httpGet);
			String xmlResult = EntityUtils.toString(httpResponse.getEntity());
			int begin = xmlResult.indexOf(smsChanel.smsResultBeginFlag);
			
			returnStatus = "No Found!";			// 预设一个缺省值
			if (begin > 0) {
				int end = xmlResult.indexOf(smsChanel.smsResultEndFlag);
				if (end > begin) {
					returnStatus = xmlResult.substring(begin + smsChanel.smsResultBeginFlag.length(), end);
					
					if (channel == CHANEL_BAK) {
						success = "ok".equals(returnStatus) || "Success".equals(returnStatus);
					} else {
						success = !returnStatus.startsWith("-");
					}
				}
			}
		} catch (Exception e) {
			returnStatus = "System Error:" + e;
			logger.error("Send SMS message", e);
		}
		
		SmsLog smsLog = new SmsLog();
		if (loginUser != null) {
			LoginUser dbLoginUser = genericService.load(LoginUser.class, loginUser.getId());
			smsLog.setLoginUser(dbLoginUser);
		}
		smsLog.setMobile(mobile);
		smsLog.setType(type);
		smsLog.setContent(content);
		smsLog.setIp(WebsiteUtil.getRemoteIp(ServletActionContext.getRequest()));
		smsLog.setSendDate(Calendar.getInstance().getTime());
		smsLog.setFree(free);
		smsLog.setFeedback(Boolean.FALSE);
		smsLog.setReturnStatus(returnStatus);
		smsLog.setExtFlag(extFlag);
		smsLog = genericService.merge(smsLog);
		
		if (!success) {
			smsLog = null;
		}
		return smsLog;
	}
	
	private String generateVerifyCode(boolean useLen4) {
		Random random = new Random(System.currentTimeMillis());
		StringBuilder code = new StringBuilder();
		int len = useLen4 ? 4 : 6;
		for (int i = 0; i < len; i++) {
			code.append(random.nextInt(9));
		}
		return code.toString();
	}
	
	public String getVerifyCode(String mobile) {
		return getVerifyCode(mobile, true);
	}
	
	public String getVerifyCode(String mobile, boolean removeIt) {
		SmsInfo smsInfo = getMobileSmsInfo(mobile);
		String code = null;
		if (smsInfo != null) {
			Calendar calendar = Calendar.getInstance();
			calendar.add(Calendar.SECOND, -600);		// 10 min
			if (smsInfo.getCreateDate().compareTo(calendar.getTime()) > 0 && smsInfo.getEnableTryCount() > 0) {
				code = smsInfo.getCode();
				
				// 更新确认标记
				SmsLog smsLog = genericService.load(SmsLog.class, smsInfo.getSmsLogId());
				smsLog.setFeedback(Boolean.TRUE);
				smsLog.setFeedbackDate(Calendar.getInstance().getTime());
				genericService.merge(smsLog);
				
				if (removeIt) {
					removeVerifyCode(smsInfo);
				}
			} else {
				removeVerifyCode(smsInfo);
			}
		}
		return code;
	}
	
	public void reduceTryCount(String mobile) {
		SmsInfo smsInfo = getMobileSmsInfo(mobile);
		if (smsInfo != null) {
			smsInfo.setEnableTryCount(smsInfo.getEnableTryCount() - 1);
			
			if (smsInfo.getEnableTryCount() <= 0) {
				removeVerifyCode(smsInfo);
			} else {
				genericService.merge(smsInfo);
			}
		}
	}
	
	public void removeVerifyCode(String phone) {
		SmsInfo smsInfo = getMobileSmsInfo(phone);
		if (smsInfo != null) {
			removeVerifyCode(smsInfo);
		}
	}
	
	public void removeVerifyCode(SmsInfo smsInfo) {
		genericService.delete(smsInfo);
		
		if (smsInfo.getSmsLogId() != null) {
			// 更新确认标记
			SmsLog smsLog = genericService.load(SmsLog.class, smsInfo.getSmsLogId());
			smsLog.setFeedback(Boolean.TRUE);
			smsLog.setFeedbackDate(Calendar.getInstance().getTime());
			genericService.merge(smsLog);
		}
	}
	
	protected String[] decrypt(String key, String seed) {
		byte[] obj = Base64.decodeBase64(key);
		try {
			String ret = new String(getCipher().doFinal(obj));
			
			if (seed != null) {
				ret = simpleDecrypt(ret, seed);
			}
			
			int index = ret.indexOf(',');
			if (index > 0) {
				return new String[] {ret.substring(0, index), ret.substring(index + 1)};
			} else {
				return new String[] {ret, null};
			}
		} catch (Exception ex) {
			//logger.error("decrypt key: " + key, ex);
			logger.error("decrypt key: " + key);
			return null;
		}
	}
	
	private String simpleDecrypt(String encrypted, String SEED) {  
        if (encrypted == null)  
            return "";  
        if (encrypted.length() == 0)  
            return "";
  
        int RADIX = 16;
        BigInteger bi_confuse = new BigInteger(SEED);
        try {  
            BigInteger bi_r1 = new BigInteger(encrypted, RADIX);  
            BigInteger bi_r0 = bi_r1.xor(bi_confuse);  
  
            return new String(bi_r0.toByteArray(), "UTF-8");  
        } catch (Exception e) {
            return "";  
        }  
    }
	
	private Cipher getCipher() {
		if (cipher == null) {
			try {
				KeyFactory keyFactory = KeyFactory.getInstance("RSA");
				// publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmxWR6AVXcHlgvu926OMA9j3vp9ul/W0sso2LlqaXmY1UoJKInkwwLcLg4f+7TOrNSghajpaFlJuJ/UUr6kMMb0WN36mRcdMREOeEXhhSBJ1b/+Eq+LO/WD8OsM6YL9FK7rBEle96pX/UT+mT0JOmJhHSc/gJUN7GWp3O4xvm4FQIDAQAB
				String priKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAKbFZHoBVdweWC+73bo4wD2Pe+n26X9bSyyjYuWppeZjVSgkoieTDAtwuDh/7tM6s1KCFqOloWUm4n9RSvqQwxvRY3fqZFx0xEQ54ReGFIEnVv/4Sr4s79YPw6wzpgv0UrusESV73qlf9RP6ZPQk6YmEdJz+AlQ3sZanc7jG+bgVAgMBAAECgYAiZNtP/i6C8w8k6Q3VdhbPm2Pm4LFPPrjGP/wJeW9pkFrjqIp4tu7o/RpBLMFIdva8sW+R2HrsrZRQxac8/dX+9SnY8Aft3XZoVYA4djDF8NIXixoPGGfmN6cpncl/xgSqmTFBu284PEE4FnKXYBlqnJLbghp+ylhj/5oEQJA4EQJBAOEZzuTgg6/daAnLBuXdGfmgDpZ4J6SGRqY9Za1It7SrKgcaPjxjKYnnQqVMAYfxFeigDWhJCDFL0bKxm5123W8CQQC9qdaYP+2TzYWEjOYAfJJBynuMrHAmi/t1FwhhycpyVL/S9GxsscBR0laHRuaXgCp16+Tf7sSfTQbLIHDBLYi7AkAvJfe26uvKR0bGXlcoiGIrbgIANFShWlqr8Uo9vHB2EC/yYuXMQjzuHJlSBBSCA9ZB7lxAjqN/p9DZa+gbWE1TAkAb+ehojs15htBSDAKFQdrFD5wH1Qu5894EmIApBH3zPWMKN/miUtned9tBmwi6gjJBMKmTCoJASTleTwuywwevAkAYW72r2iC69/OlGevf9Z+qP/ji4GmMCyieBrPHmFLXFVHXlofBuXv+tDzgTsefXu08jZCZeJSYjRBcxcHHPmXd";
				byte[] keyBytes = Base64.decodeBase64(priKey);
				PKCS8EncodedKeySpec priKeySpec = new PKCS8EncodedKeySpec(keyBytes);  
				PrivateKey privateKey = keyFactory.generatePrivate(priKeySpec);
				
				cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
				cipher.init(Cipher.DECRYPT_MODE, privateKey);
			} catch (Exception ex) {
				logger.error("Build the private key for sms service", ex);
			}
		}
		return cipher;
	}
}
